.com
Hosted by:
Unit testing expertise at your fingertips!
Home | Discuss | Lists

Dependency Lookup

The book has now been published and the content of this chapter has likely changed substanstially.
Please see page 686 of xUnit Test Patterns for the latest information.
Also known as: Service Locator, Object Factory, Component Broker, Component Registry

How do we design the system under test (SUT) so that we can replace its dependencies at run time?

The SUT asks another object to return the depended-on object before it uses it.

Sketch Dependency Lookup embedded from Dependency Lookup.gif

Almost every piece of code depends on some other classes, objects, modules or procedures. To unit test a piece of code properly, we would like to isolate it from its dependencies. This is hard to do if those dependencies are hard-coded within the code in the form of literal class names.

Dependency Lookup is a way to allow the normal coupling between a SUT and its dependencies to be broken during automated testing.

How It Works

We avoid hard-coding the names of classes on which we depend into our code because static binding severely limits our options for how the software is configured as it runs. Instead, we hard-code that name of a "component broker" that returns to us a ready to use object. The component broker provides some means for the client software or perhaps a system configuration manager to tell the SUT in question what objects to use for each component request.

When To Use It

Dependency Lookup is most appropriate when we need to retrieve depended-on components (DOCs) deep inside the system and it would be too messy to pass the Test Double (page X) in from the client. A good example of this is when we want to replace the data access layer of the system with a Fake Database (see Fake Object on page X) or In-Memory Database (see Fake Object) to speed up execution of automated customer tests. It would be to complex to have each Subcutaneous Test (see Layer Test on page X) pass the Fake Database in through the Service Facade[CJ2EEP] and all the way down to the data access layer. Using Dependency Lookup allows the test or even a Setup Decorator (page X) to use a "configuration facade" to install the Fake Database and have the SUT magically use it without any further ado. Jeremy Miller writes:

You cannot understate the value of using a Service Locator for automated testing. We routinely use alternative dependencies in testing, both to deal with difficult dependencies and for test performance. For example, in a functional test we’ll collapse a web site and a backing application server into a single process for better performance.

Dependency Lookup tends to be a lot simpler to retrofit onto existing legacy software because it only impacts those places where object construction actually occurs; we do not need to modify every intermediate object or method as we might have to do with Dependency Injection (page X). It is also a lot simpler to retrofit existing round trip tests to use a Fake Object to speed them up by wrapping them in a Setup Decorator because we do not have to change each test. That is because we can create new instances of the SUT in each test and still have it use the same Fake Object because the Service Locator remembers it across tests(We call these tests "bimodal" or "multimodal" because they can be run with both real and fake DOCs.).

The main alternative to Dependency Lookup is to provide a substitution mechanism within the SUT using Dependency Injection. This is generally preferable for unit tests as it makes the replacement of the DOC more obvious and directly connected to exercising the SUT. Another option is to use Aspect-Oriented Programming (AOP) to install test-specific logic using the development tools rather than modifying the design of the software. The least preferred solution is the use of a Test Hook (page X) within the SUT to avoid calling the DOC or within the DOC to behave in a test-specific way.

The well-known intermediary may be called a "Service Locator", an "Object Factory", a "Component Broker" or a "Component Registry". While these names imply different semantics (new vs. existing objects) this need not be the case. For performance reasons, we may choose to return new objects from a "Service Locator" or "previously enjoyed" objects from an Object Factory. To simplify this discussion, I shall use "Component Broker" in this discussion.

Implementation Notes

A desire to use a Test Double when testing our code implies a need to make DOCs substitutable. This rules out hard-coding the names of classes on which we depend into our code because static binding severely limits our options for how the software is configured as it runs. One way to avoid it is to have the SUT delegate DOC fabrication to another object. That implies we need a way to get a reference to that object. We solve this recursive problem by having a well-known object act as an intermediary between us and the DOC. This well-known object is referenced by a hard-coded class name. To be useful for installing Test Doubles this well-known object must supply a mechanism by which the test can specify the object to be returned.

Dependency Lookup is characterized by the following:

The Dependency Lookup mechanism returns an object that can be used directly by the client. The nature of the actual object returned determines whether it is more appropriate to call it a "Service Locator" or an "Object Factory". Once retrieved, the SUT uses the object directly. During testing, the test arranges for the Dependency Lookup mechanism to return a test-specific object.

Encapsulated Implementation

A main requirement of Dependency Lookup is that we have a well-known object to which we can delegate our requests for DOCs. This well-known object could be a Singleton or a Registry or some kind of Thread-Specific Storage mechanism.(The main difference is that a Singleton only has a single instance while a Registry makes no such promise. Thread-Specific Storage allows objects to access "global" data via a well-known object where the data accessed is specific to a particular thread; the same object might retrieve different data depending on which thread it is being run.)

The "Component Broker" should encapsulate its implementation from the client (our SUT.) That is, the interface provided by the "Component Broker" should not expose whether it is a Singleton or a Registry and whether some type of Thread-Specific Storage mechanism is in use under the covers. In fact, the test environment may want to provide a different implementation specifically to avoid issues caused by Singletons in tests.

such as a Substitutable Singleton (see Test-Specific Subclass on page X),

Substitution Mechanism

When a test wants to replace the real depended-on component with a Test Double, it needs a way to tell the "Component Broker" what Test Double to return instead of the real component. The "Component Broker" may provide a configuration interface to configure the object to be returned or the test can replace the component registry with a suitable Test-Specific Subclass. It may also have to provide a way to restore the original or default configuration of the broker so that the configuration used in one test does not "leak" into another test effectively changing the broker into a Shared Fixture (page X).

A less desirable configuration alternative is to have the "Component Broker" read the class names to be constructed for each request from a configuration file. This poses several problems. First, the test must write the file as part of fixture set up unless there is a way to replace the file access mechanism from the test. This is sure to result in Slow Tests (page X). Second, this will not work with Configurable Test Double (page X) unless the configuration file can also provide initialization data for the object. Finally, the need to write a file opens the door to Interacting Tests (see Erratic Test on page X) because different tests may need different configuration information.

If the "Component Broker" must return objects based on configuration data, a better solution is to have a separate Humble Object (page X) read the file and call a configuration interface on the "Component Broker". The test can then use this same interface to configure the broker on a per-test basis.

Motivating Example

This is an example of a test which cannot be made to pass "as is":

   public void testDisplayCurrentTime_AtMidnight() {
      // fixture setup
      TimeDisplay sut = new TimeDisplay();
      // exercise sut
      String result = sut.getCurrentTimeAsHtmlFragment();
      // verify direct output
      String expectedTimeString = "<span class=\"tinyBoldText\">Midnight</span>";
      assertEquals( expectedTimeString, result);
   }
Example NondeterministicTest embedded from java/com/clrstream/ex7/test/TimeDisplayTest.java

This test almost always fails because it depends on the current time being returned to the SUT by a depended-on component. The values being returned by that component, the DefaultTimeProvider, cannot be controlled by the test. Therefore, this test will only pass when the system time is exactly midnight.

   public String getCurrentTimeAsHtmlFragment() {
      Calendar currentTime;
      try {
         currentTime = new DefaultTimeProvider().getTime();
      } catch (Exception e) {
         return e.getMessage();
      }
      // etc.}
Example HardCodedDependency embedded from java/com/clrstream/ex7/problem/TimeDisplay.java

Because the SUT is hard-coded to use a particular class to retrieve the time, there is no way to replace the depended-on component with a Test Double. That makes this test nondeterministic and pretty much useless. We need to find a way to get control of the indirect inputs of the SUT.

Refactoring Notes

The first step to making this behavior testable is to replace the hard-coded class name with a call to a "Service Locator".

   public String getCurrentTimeAsHtmlFragment() {
      Calendar currentTime;
      try {
         TimeProvider timeProvider = (TimeProvider) ServiceLocator.getInstance().findService("Time");
         currentTime = timeProvider.getTime();
      } catch (Exception e) {
         return e.getMessage();
      }
      // etc.
Example ServiceLocatorCall embedded from java/com/xunitpatterns/dft/lookup/TimeDisplay.java

We could have provided a class method to avoid the chained method calls but this would just move the getInstance into the class method. The next refactoring step depends on whether we have a configuration interface on our "Service Locator". If it makes sense to configure the production version of the "Service Locator" we can introduce the configuration mechanism directly into it. This is illustrated in the next example. Otherwise, we can simply override what it returns in a Test-Specific Subclass; this is illustrated in the second example.

Example: Configurable Registry

This version of the test has been modified to use the configuration interface on the "Service Locator" to install a Test Double:

public void testDisplayCurrentTime_AtMidnight_CSL() {
      // Fixture setup:
      //      Test Double configuration
      MidnightTimeProvider tpStub = new MidnightTimeProvider();
      //   Instantiate SUT:
      TimeDisplay sut = new TimeDisplay();
      //      Test Double installation
      ServiceLocator.getInstance().registerServiceForName(tpStub, "Time");
      // exercise sut
      String result = sut.getCurrentTimeAsHtmlFragment();
      // verify outcome
      String expectedTimeString = "<span class=\"tinyBoldText\">Midnight</span>";
      assertEquals("Midnight", expectedTimeString, result);
   }
Example ServiceLocatorConfigurationTest embedded from java/com/xunitpatterns/dft/lookup/TimeDisplayServiceLocatorTest.java

The code in the SUT was described previously. The code for the Configuration Interface (see Configurable Test Double) of the Configurable Registry is:

public class ServiceLocator {
   protected ServiceLocator() {};  
  
   protected static ServiceLocator soleInstance = null;
  
   public static ServiceLocator getInstance() {
      if (soleInstance==null)
         soleInstance = new ServiceLocator();
      return soleInstance;
   }
        
   private HashMap providers = new HashMap();

   public ServiceProvider findService(String serviceName) {
      return (ServiceProvider) providers.get(serviceName);
   }
  
   // configuration interface:
   public void registerServiceForName( ServiceProvider provider, String serviceName) {
      providers.put( serviceName, provider);
   }
}
Example ConfigurableServiceLocator embedded from java/com/xunitpatterns/dft/lookup/ServiceLocator.java

The interesting thing about this example is how we have a Configuration Interface on a production class rather than a Test Double. In fact, the Configurable Registry is acting as its own Test Double by providing the test with a mechanism to alter the service component it returns.

Example: Substituted Singleton

This version of the test deals with a non-configurable Dependency Lookup mechanism by replacing the soleInstance of the "Service Locator" with a Substituted Singleton (see Test-Specific Subclass). For reusability of the configuration interface of the Substituted Singleton, we pass the TimeProvider Test Stub (page X) as an argument of overrideSoleInstance.

   public void testDisplayCurrentTime_AtMidnight_TSS() {
      // Fixture setup:
      //      Test Double configuration
      MidnightTimeProvider tpStub = new MidnightTimeProvider();
      //   Instantiate SUT:
      TimeDisplay sut = new TimeDisplay();
      //      Test Double installation
      //       Replaces entire service locator with one that
      //       always returns our Test Stub:
      ServiceLocatorTestSingleton.overrideSoleInstance(tpStub);  
      // exercise sut
      String result = sut.getCurrentTimeAsHtmlFragment();
      // verify outcome
      String expectedTimeString = "<span class=\"tinyBoldText\">Midnight</span>";
      assertEquals("Midnight", expectedTimeString, result);
   }
Example ServiceLocatorSubclassingTest embedded from java/com/xunitpatterns/dft/lookup/TimeDisplayServiceLocatorTest.java

Note how the test overrides the object normally returned by getInstance with an instance of a Test-Specific Subclass. The code for the Singleton is:

public class ServiceLocator {
   protected ServiceLocator() {};  
  
   protected static ServiceLocator soleInstance = null;
  
   public static ServiceLocator getInstance() {
      if (soleInstance==null)
         soleInstance = new ServiceLocator();
      return soleInstance;
   }
        
   private HashMap providers = new HashMap();

   public ServiceProvider findService(String serviceName) {
      return (ServiceProvider) providers.get(serviceName);
   }
}
Example SubclassableServiceLocator embedded from java/com/xunitpatterns/dft/lookup/ServiceLocator.java

Note that we have had to make the constructor and soleInstance protected rather than private to allow them to be overriden by the subclass. Finally, here is the code for the Substituted Singleton:

public class ServiceLocatorTestSingleton extends ServiceLocator {
   private ServiceProvider tpStub;
  
   private ServiceLocatorTestSingleton(TimeProvider newTpStub) {
      this.tpStub = newTpStub;
   };

   // Installation Interface:
   static ServiceLocatorTestSingleton overrideSoleInstance(TimeProvider tpStub) {
      // we could save the real instance before reassigning
      // soleInstance so we could restore it later, but we'll
      // forego that complexity for this example.
      soleInstance = new ServiceLocatorTestSingleton( tpStub);
      return (ServiceLocatorTestSingleton) soleInstance;
   } 
  
   // Overridden superclass method:
   public ServiceProvider findService(String serviceName) {
      return tpStub;  // hard-coded; ignores serviceName
   }
}
Example ServiceLocatorTestSingleton embedded from java/com/xunitpatterns/dft/lookup/ServiceLocatorTestSingleton.java

Because it cannot see the private HashMap of providers, it just returns the contents of the tpStub field that it initialized in the constructor.

About the Name

Naming this patterns was tough. Service Locator (see Dependency Lookup on page X) and Component Broker (see Dependency Lookup) were already in widespread use. These are both good names for use when describing an object that plays the. Unfortunately, neither name can encompass the other so I had to come up with another name. For consistency with Dependency Injection I decided to go with Dependency Lookup. See the sidebar What's in a (Pattern) Name? (page X) for more on this decision making process.



Page generated at Wed Feb 09 16:39:34 +1100 2011

Copyright © 2003-2008 Gerard Meszaros all rights reserved

All Categories
Introductory Narratives
Web Site Instructions
Code Refactorings
Database Patterns
DfT Patterns
External Patterns
Fixture Setup Patterns
Fixture Teardown Patterns
Front Matter
Glossary
Misc
References
Result Verification Patterns
Sidebars
Terminology
Test Double Patterns
Test Organization
Test Refactorings
Test Smells
Test Strategy
Tools
Value Patterns
XUnit Basics
xUnit Members
All "DfT Patterns"
Dependency Injection
Dependency Lookup
--Service Locator
--Object Factory
--Component Broker
--Component Registry
Humble Object
Test Hook